package com.video.service.impl;

import com.alibaba.fastjson.JSON;
import com.api.client.IdClient;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.constant.RedisKey;
import com.common.constant.RocketMqProperties;
import com.common.constant.VideoStatus;
import com.common.context.UserContext;
import com.common.domain.dto.DraftVideoAddDTO;
import com.common.domain.dto.DraftVideoQueryByConditionDTO;
import com.common.domain.dto.DraftVideoUpdateDTO;
import com.common.domain.dto.VideoPostMqDTO;
import com.common.domain.po.*;
import com.common.domain.vo.DraftVideoQueryByConditionVO;
import com.common.domain.vo.DraftVideoQueryByIdVO;
import com.common.domain.vo.DraftVideoQueryByPageVO;
import com.common.domain.vo.DraftVideoQueryVideoAndItemById;
import com.common.exception.CodeAndMsg;
import com.common.exception.JavaSmException;
import com.common.utils.AliOSSUtils;
import com.video.mapper.DraftVideoItemMapper;
import com.video.mapper.DraftVideoMapper;
import com.video.service.*;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author 路亚
 * @version 0.1
 * @className DraftVideoServiceImpl
 * @description 实现草稿视频服务接口，处理草稿视频的添加和更新逻辑
 * @date 2024/12/10 22:44
 * @since jdk11
 */
@Service
@RequiredArgsConstructor
public class DraftVideoServiceImpl extends ServiceImpl<DraftVideoMapper, DraftVideo> implements DraftVideoService {

    // 定义视频主键类型常量
    private static final String VIDEO_PK_TYPE = "video";
    // 定义视频项主键类型常量
    private static final String VIDEO_ITEM_PK_TYPE = "video_item";

    private static final String PRODUCER_GROUP = "video";
    // 定义视频基础主键ID
    private static final long VIDEO_BASE_PK = 1000000L;
    // 定义视频项基础主键ID
    private static final long VIDEO_ITEM_BASE_PK = 1000000000L;

    // 注入草稿视频Mapper
    private final DraftVideoMapper draftVideoMapper;
    // 注入草稿视频项服务
    private final DraftVideoItemService draftVideoItemService;
    // 注入草稿视频项Mapper
    private final DraftVideoItemMapper draftVideoItemMapper;

    private final VideoService videoService;

    private final VideoItemService videoItemService;

    // 注入ID客户端，用于生成主键
    private final IdClient idClient;
    // 注入阿里OSS工具类，用于文件操作
    private final AliOSSUtils aliOSSUtils;

    private final RocketMqProperties rocketMqProperties;

    private final ThreadPoolTaskExecutor taskExecutor;

    private final StringRedisTemplate stringRedisTemplate;

    private final SupplierService supplierService;

    /**
     * 添加草稿视频
     *
     * @param draftVideoAddDTO 草稿视频添加DTO，包含草稿视频的基本信息
     */
    @Override
    @Transactional
    public void add(DraftVideoAddDTO draftVideoAddDTO) {
        // 创建DraftVideo对象并复制属性
        DraftVideo draftVideo = new DraftVideo();
        BeanUtils.copyProperties(draftVideoAddDTO, draftVideo);
        // 设置创建时间和更新时间
        draftVideo.setCreateTime(LocalDateTime.now());
        draftVideo.setUpdateTime(LocalDateTime.now());
        // 处理文件移动并更新封面URL
        Long id = VIDEO_BASE_PK + idClient.getLongKeyInterval(VIDEO_PK_TYPE);
        String newFileName = moveFile(draftVideo, id);
        draftVideo.setCoverUrl(newFileName);
        // 设置主键ID
        draftVideo.setId(id);
        // 生成视频项ID并创建视频项列表
        List<Long> idList = idClient.getLongKeyInterval(VIDEO_ITEM_PK_TYPE, draftVideo.getTotalCount());
        List<DraftVideoItem> draftVideoItemList = new ArrayList<>();
        Long firstId = idList.get(0) + VIDEO_ITEM_BASE_PK;
        for (Integer i = 1; i < draftVideo.getTotalCount() + 1; i++) {
            DraftVideoItem draftVideoItem = new DraftVideoItem();
            draftVideoItem.setId(firstId + i -1);
            draftVideoItem.setVideoId(id);
            draftVideoItem.setNum(i);
            draftVideoItem.setStars(0);
            draftVideoItemList.add(draftVideoItem);
        }
        // 设置第一集ID并保存视频项
        draftVideo.setFirst(idList.get(0) + VIDEO_ITEM_BASE_PK);
        draftVideoItemService.saveBatch(draftVideoItemList);
        // 设置创建者和更新者
        draftVideo.setCreater(UserContext.getCurrentId());
        draftVideo.setUpdater(UserContext.getCurrentId());
        // 保存草稿视频
        save(draftVideo);
        // 补充，给供应商添加数据
        taskExecutor.execute(() -> {
            Supplier supplier = supplierService.getById(draftVideo.getSupplierId());
            supplier.setVideoNum(supplier.getVideoNum() + 1);
            supplierService.updateById(supplier);
        });
    }

    /**
     * 根据草稿ID更新草稿视频
     *
     * @param draftVideoUpdateDTO 草稿视频更新DTO，包含更新的草稿视频信息
     */
    @Override
    @Transactional
    public void updateByDraftId(DraftVideoUpdateDTO draftVideoUpdateDTO) {
        // 获取草稿视频
        DraftVideo draftVideo = draftVideoMapper.selectById(draftVideoUpdateDTO.getId());
        // 检查视频名称是否重复
        if (draftVideo.getName().equals(draftVideoUpdateDTO.getName())){
            throw new JavaSmException(CodeAndMsg.VIDEO_NAME_REPEAT);
        }
        // 判断视频状态并更新
        if (draftVideoUpdateDTO.getStatus() == 2 || draftVideoUpdateDTO.getTotalCount() == null){
            BeanUtils.copyProperties(draftVideoUpdateDTO, draftVideo);
            String newFileName = moveFile(draftVideo, draftVideoUpdateDTO.getId());
            draftVideo.setCoverUrl(newFileName);
            draftVideo.setUpdateTime(LocalDateTime.now());
            draftVideo.setUpdater(UserContext.getCurrentId());
            updateById(draftVideo);
        }
        // 如果总集数修改，需要重新上传所有视频文件并删除OSS中的文件
        if (draftVideoUpdateDTO.getStatus() == 0){
            // 1.删除所有videoId为id的短剧行数据
            Long videoId = draftVideoUpdateDTO.getId();
            draftVideoItemMapper.deleteByVideoId(videoId);
            // 2.调用id-service重新生成短剧行信息
            // 生成视频项ID并创建视频项列表
            List<Long> idList = idClient.getLongKeyInterval(VIDEO_ITEM_PK_TYPE, draftVideoUpdateDTO.getTotalCount());
            List<DraftVideoItem> draftVideoItemList = new ArrayList<>();
            Long firstId = idList.get(0) + VIDEO_ITEM_BASE_PK;
            for (Integer i = 1; i < draftVideoUpdateDTO.getTotalCount() + 1; i++) {
                DraftVideoItem draftVideoItem = new DraftVideoItem();
                draftVideoItem.setId(firstId + i -1);
                draftVideoItem.setVideoId(draftVideoUpdateDTO.getId());
                draftVideoItem.setNum(i);
                draftVideoItem.setStars(0);
                draftVideoItemList.add(draftVideoItem);
            }
            // 设置第一集ID并保存视频项
            draftVideo.setFirst(idList.get(0) + VIDEO_ITEM_BASE_PK);
            draftVideoItemService.saveBatch(draftVideoItemList);
            // 3.删除阿里云oss存储的 dj/video/id目录的数据
            try {
                aliOSSUtils.deleteDirectory("dj/video/" + draftVideo.getId());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            // 4.修改短剧信息
            BeanUtils.copyProperties(draftVideoUpdateDTO, draftVideo);
            String coverUrl = draftVideo.getCoverUrl();
            String newFileName = moveFile(draftVideo, draftVideo.getId());
            draftVideo.setCoverUrl(newFileName);
            draftVideo.setUpdateTime(LocalDateTime.now());
            draftVideo.setUpdater(UserContext.getCurrentId());
            updateById(draftVideo);
        }
    }

    /**
     * 移动文件到新的路径
     *
     * @param draftVideo 草稿视频对象，包含原始封面URL
     * @param id 视频ID，用于生成新的文件路径
     * @return 新的文件名
     */
    public String moveFile(DraftVideo draftVideo, Long id){
        // 提取原始URL中的协议和主机名部分作为前缀
        String originalUrl = draftVideo.getCoverUrl();
        int lastIndexOfSlash = originalUrl.lastIndexOf('/');
        String prefix = originalUrl.substring(0, lastIndexOfSlash);
        // 提取文件名
        String fileName = originalUrl.substring(lastIndexOfSlash + 1);
        // 生成新的文件路径
        String newFilePath = "dj/video/" + id + "/" + id + ".jpg";
        // 构造新的URL，包含前缀和新的文件路径
        String newFileName = prefix + "/" + newFilePath;
        try {
            // 移动文件到新的路径
            aliOSSUtils.moveFile(fileName, newFilePath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return newFileName;
    }

    /**
     * 上架视频
     *
     * @param id 视频ID，用于查询和更新视频信息
     */
    @Override
    @Transactional
    public void postVideo(Long id) {
        // 1.上架短剧前查看短剧行是否有视频
        List<DraftVideoItem> draftVideoItemList = draftVideoItemMapper.selectByVideoId(id);
        // 使用stream流检查是否有任何videoUrl为null
        boolean hasNullVideoUrl = draftVideoItemList.stream()
                .anyMatch(item -> StringUtils.isEmpty(item.getVideoUrl()));
        // 如果有任何一个videoUrl为null，则抛出异常
        if (hasNullVideoUrl) {
            throw new JavaSmException(CodeAndMsg.VIDEO_NOT_UPLOAD);
        }
        // 2.上传视频分两步
        // 2.1修改草稿视频状态为已上架
        DraftVideo updateDraftVideo = new DraftVideo();
        updateDraftVideo.setId(id);
        updateDraftVideo.setStatus(VideoStatus.POST);
        updateDraftVideo.setUpdateTime(LocalDateTime.now());
        updateDraftVideo.setUpdater(UserContext.getCurrentId());
        updateDraftVideo.setUpTime(LocalDateTime.now());
        draftVideoMapper.updateStatus(updateDraftVideo);
        // 2.2将草稿视频的数据复制到正式表中
        DraftVideo draftVideo = draftVideoMapper.selectById(id);
        Video video = new Video();
        BeanUtils.copyProperties(draftVideo, video);
        video.setUpdateTime(LocalDateTime.now());
        video.setUpTime(LocalDateTime.now());
        video.setUpdater(UserContext.getCurrentId());
        videoService.save(video);
        // 3.将草稿行数据复制到正式表中
        List<VideoItem> videoItemList = draftVideoItemList.stream()
                .map(item -> {
                    VideoItem videoItem = new VideoItem();
                    BeanUtils.copyProperties(item, videoItem);
                    return videoItem;
                })
                .collect(Collectors.toList());
        videoItemService.saveBatch(videoItemList);
        // 发送MQ，存储到ES中 之后细说
        DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
        producer.setNamesrvAddr(rocketMqProperties.getNameSrvAddr());
        try {
            producer.start();
            Message message = new Message();
            message.setTopic(PRODUCER_GROUP);
            message.setTags("postVideo");
            VideoPostMqDTO videoPostMqDTO = new VideoPostMqDTO();
            BeanUtils.copyProperties(draftVideo, videoPostMqDTO);
            message.setBody(JSON.toJSONString(videoPostMqDTO).getBytes("utf-8"));
            SendResult send = producer.send(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        producer.shutdown();
    }

    /**
     * 下架视频
     *
     * 本方法通过删除正式表中的视频数据及其相关行数据，并更新草稿表中的视频状态为"下架"，来实现视频下架的功能
     * 选择使用事务处理，确保操作的原子性，避免数据不一致的问题
     *
     * @param id 视频ID，用于定位要下架的视频
     */
    @Override
    @Transactional
    public void downVideo(Long id) {
        // 下架短剧思路
        // 1.删除正式表短剧的数据
        videoService.removeById(id);
        // 2.删除正式表短剧行的数据
        videoItemService.removeByVideoId(id);
        // 3.修改草稿表中的状态DOWN
        DraftVideo draftVideo = new DraftVideo();
        draftVideo.setStatus(VideoStatus.DOWN);
        draftVideo.setId(id);
        draftVideo.setUpdateTime(LocalDateTime.now());
        draftVideo.setUpdater(UserContext.getCurrentId());
        draftVideoMapper.updateStatus(draftVideo);
    }

    /**
     * 根据ID删除草稿状态的短剧
     *
     * 此方法用于删除系统中的草稿状态短剧它首先检查短剧的状态，只有当短剧处于下架或草稿状态时，
     * 才允许删除如果短剧处于已发布状态，则抛出异常不允许删除
     *
     * @param id 短剧的唯一标识符通过该ID来定位需要删除的短剧
     * @throws JavaSmException 如果短剧状态为已发布，则抛出此异常
     */
    @Override
    @Transactional
    public void removeDraftById(Long id) {
        // 删除指定的短剧思路
        // 1.判断短剧状态，下架或草稿状态的短剧才能删除
        DraftVideo draftVideo = draftVideoMapper.selectById(id);
        if (draftVideo.getStatus() == VideoStatus.POST){
            throw new JavaSmException(CodeAndMsg.VIDEO_STATUS_ERROR);
        }
        // 2.删除短剧数据
        draftVideoMapper.deleteById(id);
        // 3.删除短剧行数据
        draftVideoItemMapper.deleteByVideoId(id);
    }

    /**
     * 根据条件分页查询草稿箱视频信息
     *
     * @param dto 查询条件传输对象，包含视频名称、演员、状态等条件
     * @param page 当前页码
     * @param pageSize 每页记录数
     * @return 返回查询结果，包括总记录数、总页数、视频列表等信息
     */
    @Override
    public DraftVideoQueryByPageVO queryByCondition(DraftVideoQueryByConditionDTO dto, Integer page, Integer pageSize) {
        // 初始化分页对象
        Page<DraftVideo> draftVideoPage = new Page<>(page, pageSize);
        // 初始化查询条件包装器
        QueryWrapper<DraftVideo> queryWrapper = new QueryWrapper<>();

        // 根据视频名称查询，如果名称不为空
        if (!StringUtils.isEmpty(dto.getName())){
            queryWrapper.like("name", dto.getName());
        }
        // 根据男主角查询，如果男主角不为空
        if (!StringUtils.isEmpty(dto.getActor())){
            queryWrapper.like("actor", dto.getActor());
        }
        // 根据女主角查询，如果女主角不为空
        if (!StringUtils.isEmpty(dto.getActress())){
            queryWrapper.like("actress", dto.getActress());
        }
        // 根据视频状态查询，如果状态不为空
        if (dto.getStatus() != null){
            queryWrapper.eq("status", dto.getStatus());
        }
        // 根据上传时间查询，如果上传时间不为空
        if (dto.getUpTime() != null){
            queryWrapper.ge("up_time", dto.getUpTime());
        }

        // 执行分页查询
        Page<DraftVideo> result = draftVideoMapper.selectPage(draftVideoPage, queryWrapper);

        // 初始化查询结果传输对象
        DraftVideoQueryByPageVO draftVideoQueryByPageVO = new DraftVideoQueryByPageVO();
        // 设置总记录数
        draftVideoQueryByPageVO.setTotal(result.getTotal());
        // 设置总页数
        draftVideoQueryByPageVO.setPages(result.getPages());

        // 将查询结果转换为VO列表
        List<DraftVideoQueryByConditionVO> list = result.getRecords().stream()
                .map(item -> {
                    DraftVideoQueryByConditionVO draftVideoQueryByConditionVO = new DraftVideoQueryByConditionVO();
                    // 复制属性到VO对象
                    BeanUtils.copyProperties(item, draftVideoQueryByConditionVO);
                    return draftVideoQueryByConditionVO;
                })
                .collect(Collectors.toList());

        // 设置视频列表
        draftVideoQueryByPageVO.setList(list);

        // 判断列表是否为空，设置相应标志
        if (list.size() == 0 || list == null){
            draftVideoQueryByPageVO.setEmpty(true);
        }else {
            draftVideoQueryByPageVO.setEmpty(false);
        }

        // 返回查询结果
        return draftVideoQueryByPageVO;
    }

    /**
     * 根据视频ID查询草稿视频信息
     *
     * @param id 视频的唯一标识符
     * @return 返回包含草稿视频详细信息的对象
     */
    @Override
    public DraftVideoQueryByIdVO queryById(Long id) {
        // 通过视频ID从数据库中查询草稿视频记录
        DraftVideo draftVideo = draftVideoMapper.selectById(id);

        // 创建一个用于返回结果的对象，并将查询到的草稿视频信息复制到该对象中
        DraftVideoQueryByIdVO result = new DraftVideoQueryByIdVO();
        BeanUtils.copyProperties(draftVideo, result);

        // 通过视频ID从数据库中查询所有与该视频相关的视频项
        List<DraftVideoItem> draftVideoItemList = draftVideoItemMapper.selectByVideoId(id);

        // 将查询到的视频项的URL提取出来，放入一个列表中
        List<String> itemList = draftVideoItemList.stream().map(DraftVideoItem::getVideoUrl).collect(Collectors.toList());

        // 将视频项的URL列表设置到返回结果对象中
        result.setItems(itemList);

        // 返回包含草稿视频详细信息和视频项URL列表的对象
        return result;
    }

    @Override
    public DraftVideoQueryVideoAndItemById queryVideoAndItemById(Long id, Integer num) {
        DraftVideo draftVideo = draftVideoMapper.selectById(id);
        DraftVideoQueryVideoAndItemById result = new DraftVideoQueryVideoAndItemById();
        BeanUtils.copyProperties(draftVideo, result);
        result.setItemVO(draftVideoItemMapper.selectByVideoIdAndNum(id, num));
        // 补充，添加观看次数给redis
        taskExecutor.execute(() -> {
            String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            String key = RedisKey.LIST_LOOK_NUM_KEY + format;
            stringRedisTemplate.opsForHash().increment(RedisKey.LIST_LOOK_NUM_KEY + format, id.toString(), 1);
        });
        return result;
    }
}
